package saci.util.xml;

import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.Date;

import saci.util.Types;

class XmlWriter {

    private boolean prettyPrint, prettyPrintCloseTag;
    private int level;
	private Writer writer;
    private XmlMapper mapper = new XmlMapper();

    XmlWriter(Writer writer, boolean prettyPrint) {
        setWriter(writer);
        this.prettyPrint = prettyPrint;
    }

    public void setWriter(Writer writer) {
        assert (writer != null);
        this.writer = writer;
    }

    private void write(String s) throws IOException {
        writer.write(s);
    }

    private void write(char s) throws IOException {
        writer.write(s);
    }

    private void writeValue(String value) throws IOException {
        if (value == null) {
            write("");
        }

        for (int i = 0; i < value.length(); i++) {
            switch (value.charAt(i)) {
                case '<':
                    write("&lt;");
                    break;
                case '>':
                    write("&gt;");
                    break;
                case '&':
                    write("&amp;");
                    break;
                case '"':
                    write("&quot;");
                    break;
                case '\'':
                    write("&#39;");
                    break;
                default:
                    write(value.charAt(i));
            }
        }
    }

    public void writeBean(Object bean) throws IOException {
        writeHeader(bean);
        writeBeanInfo(bean);
        writer.flush();
    }

    private void writeEncodingInfo() throws IOException {
        write("<?xml version=\"1.0\" encoding=\"");
        write(java.nio.charset.Charset.defaultCharset().name());
        write("\"?>");
    }

    private void writeHeader(Object bean) throws IOException {
        writeEncodingInfo();
    }

    private void writeBeanInfo(Object bean) throws IOException {
        try {
            XmlMap xmlMap = mapper.mapBean(bean);
            writeBeanTag(bean, xmlMap);
            writeBeanFields(bean, xmlMap);
            closeTag(xmlMap.name);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    private void writeList(Collection<?> collection) throws IOException {
        for (Object object : collection) {
            writeBeanInfo(object);
        }
    }

    private void writeBeanFields(Object bean, XmlMap xmlMap) throws InvocationTargetException, IOException {
        for (int i = 0; i < xmlMap.fields.size(); i++) {
            FieldMap fieldMap = xmlMap.fields.get(i);
            try {
                Object value = fieldMap.field.get(bean);
                if (value == null) {
                    if (fieldMap.nillable) {
                        openEmptyTag(fieldMap.name);
                    }
                } else {
                    if (value instanceof Collection<?>) {
                        writeList((Collection<?>) value);
                    } else {
                        if (fieldMap.printable) {
                            openTag(fieldMap.name);
                            if (value instanceof Date) {
                                write(Types.parseString((Date) value));
                            } else {
                                writeValue(value.toString());
                            }
                            closeTag(fieldMap.name);
                        } else {
                            writeBeanInfo(value);
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    private void writeBeanTag(Object bean, XmlMap xmlMap) throws InvocationTargetException, IOException {
    	if (prettyPrint) {
    		printLevel();
    		level++;
    		prettyPrintCloseTag = false;
    	}
        write("<");
        write(xmlMap.name);
        if (xmlMap.attributes.size() > 0) {
            for (int i = 0; i < xmlMap.attributes.size(); i++) {
                FieldMap fieldMap = xmlMap.attributes.get(i);
                try {
                    Object value = fieldMap.field.get(bean);
                    try {
                        if (value == null) {
                            if (fieldMap.required) {
                                write(' ');
                                write(fieldMap.name);
                                write("=\"\"");
                            }
                        } else {
                            write(' ');
                            write(fieldMap.name);
                            write("=\"");
                            if (value instanceof Date) {
                                write(Types.parseString((Date) value));
                            } else {
                                writeValue(value.toString());
                            }
                            write("\"");
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        if (!Types.isNullOrEmpty(xmlMap.namespace)) {
        	write(" xmlns=\"");
        	write(xmlMap.namespace);
        	write("\"");
        }
        write(">");
    }

    private void openTag(String tagName) throws IOException {
    	if (prettyPrint) {
    		printLevel();
    		level++;
    		prettyPrintCloseTag = false;
    	}
        write("<");
        write(tagName);
        write(">");
    }

    private void openEmptyTag(String tagName) throws IOException {
    	write("<");
        write(tagName);
        write("/>");
    	if (prettyPrint) {
    		prettyPrintCloseTag = false;
    		write('\n');
    	}
    }

    private void closeTag(String tagName) throws IOException {
    	if (prettyPrint) {
    		level--;
    		if (prettyPrintCloseTag) {
    			printLevel();
    		}
    		prettyPrintCloseTag = true;
    	}
        write("</");
        write(tagName);
        write(">");
    }
    
    private void printLevel() throws IOException {
    	write('\n');
    	for (int i = 0; i < level; i++) {
    		write("  ");
    	}
    }
}
